using Unity.Mathematics;
using EarthGeospatial;
using Geometry;
using System;

namespace Cesium3DTilesSelection
{
    public interface BBox
    {
        public CullingResult intersectPlane(Plane leftPlane);
    }
    public class BoundingVolume : BBox
    {
        public OrientedBoundingBox pOrientedBBox;
        public BoundingRegion pBoundingRegion;
        public BoundingRegionWithLooseFittingHeights pLooseRegion;

        public BoundingSphere pBoundingSphere;

        public BoundingVolume() { }
        public BoundingVolume(BoundingRegion boundingRegion)
        {
            this.pBoundingRegion = boundingRegion;
        }

        public BoundingVolume(BoundingRegionWithLooseFittingHeights boundingRegionWithLooseFittingHeights)
        {
            this.pLooseRegion = boundingRegionWithLooseFittingHeights;
        }

        public static BoundingVolume transformBoundingVolume(double4x4 transform, BoundingVolume boundingVolume)
        {
            if (boundingVolume.pOrientedBBox)
            {
                BoundingVolume bv = new BoundingVolume();
                double4 center = math.mul(transform, new double4(boundingVolume.pOrientedBBox._center, 1.0));
                double3x3 halfAxes = math.mul(new double3x3(transform.c0.xyz, transform.c1.xyz, transform.c2.xyz), boundingVolume.pOrientedBBox._halfAxes);

                bv.pOrientedBBox = new OrientedBoundingBox(center.xyz, halfAxes);
                return bv;
            }
            if (boundingVolume.pBoundingRegion)
            {
                return boundingVolume;
            }
            if (boundingVolume.pBoundingSphere)
            {
                double4 center = math.mul(transform, new double4(boundingVolume.pBoundingSphere._center, 1.0));

                double uniformScale = math.max(
                     math.max(
                        math.length(new double3(transform[0].xyz)),
                        math.length(new double3(transform[1].xyz))),
                 math.length(new double3(transform[2].xyz)));
                BoundingVolume bv = new BoundingVolume();
                bv.pBoundingSphere = new BoundingSphere(center.xyz, boundingVolume.pBoundingSphere._radius * uniformScale);
                return bv;
            }
            if (boundingVolume.pLooseRegion)
            {
                return boundingVolume;
            }
            return null;
        }

        public static double3 getBoundingVolumeCenter(BoundingVolume boundingVolume)
        {
            if (boundingVolume.pBoundingRegion)
            {
                return boundingVolume.pBoundingRegion._boundingBox._center;
            }
            if (boundingVolume.pLooseRegion)
            {
                return boundingVolume.pLooseRegion.getBoundingRegion()._boundingBox._center;
            }
            return double3.zero;
        }

        public static implicit operator BoundingVolume(OrientedBoundingBox self)
        {
            var target = new BoundingVolume();
            target.pOrientedBBox = self;
            return target;
        }

        public static implicit operator BoundingVolume(BoundingRegion self)
        {
            var target = new BoundingVolume();
            target.pBoundingRegion = self;
            return target;
        }

        public static implicit operator BoundingVolume(BoundingSphere self)
        {
            var target = new BoundingVolume();
            target.pBoundingSphere = self;
            return target;
        }

        public static implicit operator bool(BoundingVolume self)
        {
            return self == null ? false : true;
        }

        public CullingResult intersectPlane(Plane plane)
        {
            if (pOrientedBBox)
            {
                return pOrientedBBox.intersectPlane(plane);
            }
            if (pBoundingSphere)
            {
                return pBoundingSphere.intersectPlane(plane);
            }
            if (pBoundingRegion)
            {
                return pBoundingRegion.intersectPlane(plane);
            }
            if (pLooseRegion)
            {
                return pLooseRegion.getBoundingRegion().intersectPlane(plane);
            }
            return CullingResult.Outside;
        }
    }
}